// Copyright (c) Appeon. All rights reserved.
// Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. 
// You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0.

using System.Collections.Generic;

namespace SnapObjects.Data
{
    internal abstract class SqlSelectStatementGenerator : SqlStatementGenerator
    {
        public SqlSelectStatementGenerator(
            ISqlStatementFactory factory,
            ISqlGenerationHelper helper,
            ITypeMapper typeMapper) : base(factory, helper, typeMapper)
        {
        }

        protected override IReadOnlyList<SqlToken> SpliceStatement(SqlStatement statement)
        {
            var result = new List<SqlToken>();

            var select = (SqlSelectStatement)statement;

            this.CreateSelectKeyword(result, select);
            this.CreateSelectColumns(result, select);
            this.CreateFrom(result, select);
            this.CreateWhere(result, select);
            this.CreateGroupBy(result, select);
            this.CreateHaving(result, select);

            this.CreateUnions(result, select);

            if (select.Skip == -1)
            {
                this.CreateOrderBy(result, select);
            }

            return result;
        }

        protected virtual void CreateSelectKeyword(List<SqlToken> tokens, SqlSelectStatement select)
        {
            tokens.Add(new SqlKeywordToken(SqlKeywords.SELECT));
            tokens.Add(SqlWhitespaceToken.Whitespace);
        }

        protected virtual bool CreateSelectColumns(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.Select.Count > 0)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceClause(select.Select));

                return true;
            }

            return false;
        }

        protected virtual bool CreateFrom(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.From.Count > 0)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.FROM));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceClause(select.From));

                this.CreateTerminator(tokens, select);

                return true;
            }

            return false;
        }

        protected virtual bool CreateWhere(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.Where.Count > 0)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.WHERE));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceClause(select.Where));

                return true;
            }

            return false;
        }

        protected virtual bool CreateGroupBy(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.GroupBy.Count > 0)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.GROUP));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.BY));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceClause(select.GroupBy));

                return true;
            }

            return false;
        }

        protected virtual bool CreateHaving(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.Having.Count > 0)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.HAVING));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceClause(select.Having));

                return true;
            }

            return false;
        }

        protected virtual bool CreateOrderBy(List<SqlToken> tokens, SqlSelectStatement select)
        {
            if (select.OrderBy.Count > 0)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.ORDER));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.Add(new SqlKeywordToken(SqlKeywords.BY));
                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceClause(select.OrderBy));

                return true;
            }

            return false;
        }

        protected virtual bool CreateUnions(List<SqlToken> tokens, SqlSelectStatement select)
        {
            foreach (var union in select.Unions)
            {
                tokens.Add(SqlWhitespaceToken.Whitespace);

                switch (union.Value)
                {
                    case SqlUnionType.Union:
                    {
                        tokens.Add(new SqlKeywordToken(SqlKeywords.UNION));
                    }
                    break;
                    case SqlUnionType.UnionAll:
                    {
                        tokens.Add(new SqlKeywordToken(SqlKeywords.UNION));
                        tokens.Add(SqlWhitespaceToken.Whitespace);
                        tokens.Add(new SqlKeywordToken(SqlKeywords.ALL));
                    }
                    break;
                }

                tokens.Add(SqlWhitespaceToken.Whitespace);

                tokens.Add(new SqlCharacterToken(SqlCharacters.OpenParentheses));

                tokens.Add(SqlWhitespaceToken.Whitespace);
                tokens.AddRange(this.SpliceStatement(union.Key));

                if (tokens.Count > 0
                    && tokens[tokens.Count - 1] is SqlCharacterToken c
                    && c.Character == SqlCharacters.Semicolon)
                {
                    tokens.Insert(tokens.Count - 1, new SqlCharacterToken(SqlCharacters.CloseParentheses));
                }
                else
                {
                    tokens.Add(new SqlCharacterToken(SqlCharacters.CloseParentheses));
                }
            }

            return select.Unions.Count > 0;
        }

        protected virtual void CreateTerminator(List<SqlToken> tokens, SqlSelectStatement select)
        {

        }
    }
}
